package de.hska.master.support;

import java.util.ArrayList;
import java.util.List;

import de.hska.master.model.Point;

/**
 * Geometry support class
 */
public class Geometry {

	/**
	 * Calculates the crossProduct on a line (represented by two points) and a point
	 * 
	 * @param a
	 *            point which is not part of line
	 * @param p0
	 *            start point of line
	 * @param p1
	 *            end point of line
	 * @return crossProduct
	 */
	public double crossProduct(Point a, Point p0, Point p1) {
		double isClockwise = (a.getX() - p0.getX()) * (p1.getY() - p0.getY())
				- (p1.getX() - p0.getX()) * (a.getY() - p0.getY());
		return isClockwise;
	}

	/**
	 * Generates some random points which fulfills the following criterias:
	 * 
	 * @param numberOfPoints
	 *            number of points to generate
	 * @param radius
	 *            radius to centerPoint which all generated points must have
	 * @param centerPoint
	 *            center of the circle
	 * @return array of points which fulfills the criterias as describe above
	 */
	public Point[] getPointsInACircle(int numberOfPoints, double radius, Point centerPoint) {
		Point[] points = new Point[numberOfPoints];
		double slice = 2 * Math.PI / numberOfPoints;
		for (int i = 0; i < numberOfPoints; i++) {
			double angle = slice * i;
			double x = (centerPoint.getX() + radius * Math.cos(angle));
			double y = (centerPoint.getY() + radius * Math.sin(angle));
			Point p = new Point(x, y);
			points[i] = p;
		}
		return points;
	}

	public int getRightMostPoint(List<Point> points) {
		int index = 0;
		for (int i = 0; i < points.size(); i++) {
			if (points.get(i).getX() > points.get(index).getX()) {
				index = i;
			}
		}
		return index;
	}

	public int getLeftMostPoint(List<Point> points) {
		int index = 0;
		for (int i = 0; i < points.size(); i++) {
			if (points.get(i).getX() < points.get(index).getX()) {
				index = i;
			}
		}
		return index;
	}

	public boolean isLeft(Point a, Point p0, Point p1) {
		return crossProduct(a, p0, p1) < 0;
	}

	public boolean isRight(Point a, Point p0, Point p1) {
		return crossProduct(a, p0, p1) > 0;
	}

	public boolean isLeft(List<Point> leftSide, List<Point> rightSide, int pointLeft,
			int pointRight, int nextPointRight) {
		if (nextPointRight < 0)
			return false;

		Point a = rightSide.get(nextPointRight);
		Point p0 = leftSide.get(pointLeft);
		Point p1 = rightSide.get(pointRight);

		return isLeft(a, p0, p1);
	}

	public boolean isRight(List<Point> leftSide, List<Point> rightSide, int pointLeft,
			int pointRight, int nextPointLeft) {

		if (nextPointLeft < 0)
			return false;

		Point a = leftSide.get(nextPointLeft);
		Point p0 = rightSide.get(pointRight);
		Point p1 = leftSide.get(pointLeft);

		return isRight(a, p0, p1);
	}

	/**
	 * See http://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
	 * 
	 * @param p
	 * @param lineStart
	 * @param lineEnd
	 * @return
	 */
	public double getPerpendicularDistanceOfPointToLine(Point p, Point lineStart, Point lineEnd) {
		// (a-p) - (((a-p)*n)*n)
		Vector pAsV = new Vector(p);
		Vector lineStartAsV = new Vector(lineStart);

		Vector aMinusP = lineStartAsV.sub(pAsV);

		Vector n = new Vector(lineStart, lineEnd).normalize();

		double aMinusPN = aMinusP.scalarProduct(n);

		Vector aMinusPNN = n.mul(aMinusPN);

		Vector diff = aMinusP.sub(aMinusPNN);
		return diff.length();
	}

	public Point getFarestPointToLine(List<Point> points, Point lineStart, Point lineEnd) {
		double maxDistSoFar = Double.MIN_VALUE;
		Point returnedPoint = null;
		for (Point point : points) {
			double currentDist = getPerpendicularDistanceOfPointToLine(point, lineStart, lineEnd);
			if (currentDist > maxDistSoFar) {
				maxDistSoFar = currentDist;
				returnedPoint = point;
			}
		}
		return returnedPoint;
	}

	public List<Point> getPointsRightOfLine(List<Point> points, Point startPoint, Point endPoint) {
		ArrayList<Point> returnedPoints = new ArrayList<Point>();
		for (Point point : points) {
			boolean isRight = isRight(point, startPoint, endPoint);
			if (isRight) {
				returnedPoints.add(point);
			}
		}
		return returnedPoints;
	}
}
